﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ArithmeticLibrary.Tree;

namespace ArithmeticLibrary.Visitors
{
    /// <summary>
    /// Visitor pro derivovani aritmetickeho stromu.
    /// </summary>
    class DerivatorVisitor : IExpressionVisitor<Node, NoContext>
    {
        public Node Visit(NumberNode node, NoContext context)
        {
            return new NumberNode(0);
        }

        public Node Visit(VariableNode node, NoContext context)
        {
            return new NumberNode(1);
        }

        public Node Visit(PlusNode node, NoContext context)
        {
            Node leftDerivation = node.Left.Accept(this);
            Node rightDerivation = node.Right.Accept(this);

            return new PlusNode(leftDerivation, rightDerivation);
        }

        public Node Visit(MinusNode node, NoContext context)
        {
            Node leftDerivation = node.Left.Accept(this);
            Node rightDerivation = node.Right.Accept(this);

            return new MinusNode(leftDerivation, rightDerivation);
        }

        public Node Visit(MultiplyNode node, NoContext context)
        {
            Node leftDerivation = node.Left.Accept(this);
            Node rightDerivation = node.Right.Accept(this);

            MultiplyNode left = new MultiplyNode(leftDerivation, node.Right);
            MultiplyNode right = new MultiplyNode(node.Left, rightDerivation);

            return new PlusNode(left, right);
        }

        public Node Visit(DivideNode node, NoContext context)
        {
            Node leftDerivation = node.Left.Accept(this);
            Node rightDerivation = node.Right.Accept(this);

            MultiplyNode mulLeft = new MultiplyNode(leftDerivation, node.Right);
            MultiplyNode mulRight = new MultiplyNode(node.Left, rightDerivation);

            MinusNode top = new MinusNode(mulLeft, mulRight);
            PowerNode bot = new PowerNode(node.Right, new NumberNode(2));

            return new DivideNode(top, bot);
        }

        public Node Visit(PowerNode node, NoContext context)
        {
            // pokud jsou zaklad i mocnina konstanta
            // tj. derivujeme jen konstantu
            if (node.Left is NumberNode && node.Right is NumberNode)
            {
                return new NumberNode(0);
            }
            // jinak pokud zaklad je konstanta
            else if (node.Left is NumberNode)
            {
                // zaklad mocniny
                NumberNode b = (NumberNode)node.Left;

                // zderivujeme vnitrni funkci
                Node der = node.Right.Accept(this);

                // zderivujeme mocninu
                MultiplyNode mul = new MultiplyNode(node, new LogarithmNode(new NumberNode(b.Value)));
                
                // vratime derivaci krat derivaci vnitrku
                return new MultiplyNode(mul, der);
            }
            // jinak pokud mocnina je konstanta
            else if (node.Right is NumberNode)
            {
                // mocnina
                NumberNode p = (NumberNode)node.Right;

                // derivace vnitrku
                Node der = node.Left.Accept(this);

                // zderivujeme mocninu
                MultiplyNode mul = new MultiplyNode(new NumberNode(p.Value), new PowerNode(node.Left, new NumberNode(p.Value - 1)));

                // vratime derivaci krat derivaci vnitrku
                return new MultiplyNode(mul, der);
            }
            // jinak jsou zaklad i mocnina funkce
            else
            {
                Node y = new MultiplyNode(node.Right, new LogarithmNode(node.Left));

                Node der = y.Accept(this);

                return new MultiplyNode(node, der);

            }
        }
        public Node Visit(UnaryMinusNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            return new UnaryMinusNode(childDerivation);
        }

        public Node Visit(SineNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            CosineNode cos = new CosineNode(node.Child);

            return new MultiplyNode(cos, childDerivation);
        }

        public Node Visit(CosineNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            SineNode sin = new SineNode(node.Child);
            UnaryMinusNode minus = new UnaryMinusNode(sin);

            return new MultiplyNode(minus, childDerivation);
        }

        public Node Visit(TangentNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            CosineNode cos = new CosineNode(node.Child);
            PowerNode pow = new PowerNode(cos, new NumberNode(2));
            DivideNode div = new DivideNode(new NumberNode(1), pow);

            return new MultiplyNode(div, childDerivation);
        }

        public Node Visit(ExponentialNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            return new MultiplyNode(new ExponentialNode(node.Child), childDerivation);
        }

        public Node Visit(LogarithmNode node, NoContext context)
        {
            Node childDerivation = node.Child.Accept(this);

            DivideNode div = new DivideNode(new NumberNode(1), node.Child);

            return new MultiplyNode(div, childDerivation);
        }

        public Node Derivate(Node node)
        {
            return node.Accept(this);
        }
    }
}
